package com.lemon.exam.common.filter;

import com.lemon.exam.common.constant.ApiConst;
import com.lemon.exam.common.constant.RedisKeyConst;
import com.lemon.exam.common.entity.system.LoginUser;
import com.lemon.exam.common.entity.system.SysWeb;
import com.lemon.exam.common.enums.RouteEnum;
import com.lemon.exam.common.redis.RedisService;
import com.lemon.exam.common.util.RequestUtil;
import com.lemon.exam.common.util.SecurityUtil;
import com.lemon.exam.entity.po.LoggingPO;
import com.lemon.exam.service.ILoggingService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuple3;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 操作日志过滤器
 *
 * @author Lemon
 * @since 2025/3/28 11:03
 */
@Slf4j
@Component
@Order(Ordered.LOWEST_PRECEDENCE - 100)//优先级较低
public class RequestLoggingFilter implements WebFilter {
    /**
     * 路径缓存
     * key: 请求路径
     * value: 名称
     */
    private static final Map<PathPattern, String> PATH_NAME_CACHE = new ConcurrentHashMap<>();
    private static final PathPatternParser PATH_PATTERN_PARSER = new PathPatternParser();

    private volatile boolean cacheInitialized = false;

    @Autowired
    @Qualifier("logScheduler")
    private Scheduler logScheduler;

    @Resource
    private RedisService redisService;

    @Resource
    private ILoggingService loggingService;

    @Override
    @NonNull
    public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
        //执行开始时间
        Instant start = Instant.now();
        //请求的 Content-Type
        MediaType contentType = exchange.getRequest().getHeaders().getContentType();
        log.info("日志过滤器, 请求路径: {}, 请求方法: {}, 媒体类型: {}, 请求时间: {}", exchange.getRequest().getPath(), exchange.getRequest().getMethod(), contentType, start);

        return handleRequestBasedOnContentType(exchange, chain, start, contentType).onErrorResume(e -> {
            log.error("请求处理异常", e);
            return chain.filter(exchange); // 继续处理请求即使日志记录失败
        });
    }

    /**
     * 根据请求内容类型处理请求
     *
     * @param exchange
     * @param chain
     * @param start
     * @param contentType
     * @return
     */
    private Mono<Void> handleRequestBasedOnContentType(ServerWebExchange exchange, WebFilterChain chain, Instant start, MediaType contentType) {
        if (isMultipartRequest(contentType)) {
            return chain.filter(exchange).then(logRequest(exchange, "文件上传", start));
        } else if (isJsonRequest(contentType)) {
            return handleJsonRequest(exchange, chain, start);
        } else {
            return chain.filter(exchange).then(logRequest(exchange, null, start));
        }
    }

    /**
     * 判断是否为json请求
     *
     * @param contentType
     * @return
     */
    private boolean isJsonRequest(MediaType contentType) {
        return contentType != null && contentType.includes(MediaType.APPLICATION_JSON);
    }

    /**
     * 判断是否为文件上传请求
     *
     * @param contentType
     * @return
     */
    private boolean isMultipartRequest(MediaType contentType) {
        return contentType != null && (contentType.includes(MediaType.MULTIPART_FORM_DATA) ||
                contentType.includes(MediaType.IMAGE_JPEG) ||
                contentType.includes(MediaType.IMAGE_GIF) ||
                contentType.includes(MediaType.IMAGE_PNG));
    }


    /**
     * 处理json请求
     *
     * @param exchange
     * @param chain
     * @param start
     * @return
     */
    private Mono<Void> handleJsonRequest(ServerWebExchange exchange, WebFilterChain chain, Instant start) {
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            try {
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);

                String requestBody = new String(bytes, StandardCharsets.UTF_8);
                ServerHttpRequest decoratedRequest = new SystemServerHttpRequestDecorator(exchange.getRequest(), bytes);

                return chain.filter(exchange.mutate().request(decoratedRequest).build()).then(logRequest(exchange, requestBody, start));
            } catch (Exception e) {
                log.error("请求处理异常", e);
                DataBufferUtils.release(dataBuffer);
                return Mono.error(e);
            }
        });
    }

    /**
     * 记录请求日志
     *
     * @param exchange
     * @param requestBody
     * @param start
     */
    private Mono<Void> logRequest(ServerWebExchange exchange, String requestBody, Instant start) {
        return Mono.defer(() -> {
            final ServerHttpRequest request = exchange.getRequest();
            final ServerHttpResponse response = exchange.getResponse();

            //并行获取所需数据
            return Mono.zip(
                    SecurityUtil.getLoginUser(),
                    redisService.id().get(RedisKeyConst.GLOBAL_ID + ApiConst.ES_LOGGING_INDEX),
                    getPathName(request.getURI().getPath()).defaultIfEmpty("未知路径")
            ).flatMap(tuple -> {
                final LoggingPO logging = buildLoggingPO(request, response, tuple, requestBody, start, Instant.now());
                return loggingService.insert(logging).onErrorResume(e -> Mono.empty());
            }).subscribeOn(logScheduler);
        });
    }

    /**
     * 构建日志对象
     *
     * @param request
     * @param response
     * @param tuple
     * @param requestBody
     * @param start
     * @param end
     * @return
     */
    @SuppressWarnings("ConstantConditions")
    private LoggingPO buildLoggingPO(ServerHttpRequest request, ServerHttpResponse response, Tuple3<LoginUser, Long, String> tuple, String requestBody, Instant start, Instant end) {
        LoggingPO logging = new LoggingPO();
        logging.setId(tuple.getT2());
        logging.setIp(RequestUtil.getClientIp(request));
        logging.setUri(request.getURI().getPath());
        logging.setContent(tuple.getT3());
        logging.setMethod(request.getMethod().name());
        logging.setParam(request.getQueryParams().toString());
        logging.setCode(response.getStatusCode() != null ? response.getStatusCode().value() : 0);
        logging.setDuration(Duration.between(start, end).toMillis());
        logging.setCreateTime(end.toEpochMilli());

        if (StringUtils.hasText(requestBody)) {
            logging.setBody(requestBody);
        }

        LoginUser user = tuple.getT1();
        if (user.getId() != null) {
            logging.setUserId(user.getId());
            logging.setUsername(user.getUsername());
        }

        return logging;
    }

    /**
     * 获取路径名称
     *
     * @param requestPath
     * @return
     */
    private Mono<String> getPathName(String requestPath) {
        return Mono.defer(() -> {
            if (!cacheInitialized || PATH_NAME_CACHE.isEmpty()) {
                return loadPathNames()
                        .then(Mono.fromCallable(() -> findPathName(requestPath)))
                        .subscribeOn(Schedulers.boundedElastic());
            }
            return Mono.fromCallable(() -> findPathName(requestPath))
                    .subscribeOn(Schedulers.boundedElastic());
        });
    }

    /**
     * 获取路径名称
     *
     * @param requestPath
     * @return
     */
    private String findPathName(String requestPath) {
        return PATH_NAME_CACHE.entrySet().parallelStream()
                .filter(entry -> entry.getKey().matches(PathContainer.parsePath(requestPath)))
                .findFirst()
                .map(Map.Entry::getValue)
                .orElse(findPathNameRouter(requestPath));
    }

    /**
     * 获取路径名称
     *
     * @param requestPath
     * @return
     */
    private String findPathNameRouter(String requestPath) {
        return Arrays.stream(RouteEnum.values())
                .collect(Collectors.toMap(RouteEnum::getPath, RouteEnum::getDescription))
                .entrySet()
                .parallelStream()
                .filter(entry -> PATH_PATTERN_PARSER.parse(entry.getKey()).matches(PathContainer.parsePath(requestPath)))
                .findFirst()
                .map(Map.Entry::getValue)
                .orElse(null);
    }

    /**
     * 载路径名称
     *
     * @return
     */
    private Mono<Void> loadPathNames() {
        return redisService.set().get(RedisKeyConst.SYSTEM_INTERFACE, SysWeb.class)
                .collectList()
                .flatMap(this::updateCacheWithSystemInterfaces)
                .doOnSuccess(v -> cacheInitialized = true)
                .doOnError(e -> log.error("加载路径名称失败", e))
                .onErrorResume(e -> Mono.empty());
    }

    /**
     * 更新缓存
     *
     * @param sysWebs
     * @return
     */
    private Mono<Void> updateCacheWithSystemInterfaces(List<SysWeb> sysWebs) {
        return Mono.fromRunnable(() -> {
            Map<PathPattern, String> tempMap = sysWebs.stream()
                    .flatMap(sysWeb -> sysWeb.methods().stream()
                            .map(method -> Map.entry(PATH_PATTERN_PARSER.parse(method.path()), sysWeb.name() + " - " + method.name())))
                    .collect(Collectors.toConcurrentMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            (existing, replacement) -> existing
                    ));

            PATH_NAME_CACHE.clear();
            PATH_NAME_CACHE.putAll(tempMap);
            log.info("已加载 {} 条路径映射", tempMap.size());
        });
    }

    /**
     * 请求体装饰器
     */
    private static class SystemServerHttpRequestDecorator extends ServerHttpRequestDecorator {
        private final byte[] bytes;

        public SystemServerHttpRequestDecorator(ServerHttpRequest delegate, byte[] bytes) {
            super(delegate);
            this.bytes = bytes;
        }

        @NotNull
        @Override
        public Flux<DataBuffer> getBody() {
            return Flux.just(DefaultDataBufferFactory.sharedInstance.wrap(bytes));
        }
    }
}
